using System;
using System.Reflection;
using System.Runtime.Serialization;

namespace CloningExtension
{
	public class NonSerialiazableTypeSurrogateSelector : ISerializationSurrogate, ISurrogateSelector
	{
		public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
		{
			FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (this.IsKnownType(fieldInfo.FieldType))
				{
					info.AddValue(fieldInfo.Name, fieldInfo.GetValue(obj));
				}
				else if (fieldInfo.FieldType.IsClass)
				{
					info.AddValue(fieldInfo.Name, fieldInfo.GetValue(obj));
				}
			}
		}

		private bool IsKnownType(Type type)
		{
			return type == typeof(string) || type.IsPrimitive || type.IsSerializable;
		}

		public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
		{
			FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (this.IsKnownType(fieldInfo.FieldType))
				{
					if (this.IsNullableType(fieldInfo.FieldType))
					{
						Type firstArgumentOfGenericType = this.GetFirstArgumentOfGenericType(fieldInfo.FieldType);
						fieldInfo.SetValue(obj, info.GetValue(fieldInfo.Name, firstArgumentOfGenericType));
					}
					else
					{
						fieldInfo.SetValue(obj, info.GetValue(fieldInfo.Name, fieldInfo.FieldType));
					}
				}
				else if (fieldInfo.FieldType.IsClass)
				{
					fieldInfo.SetValue(obj, info.GetValue(fieldInfo.Name, fieldInfo.FieldType));
				}
			}
			return obj;
		}

		private Type GetFirstArgumentOfGenericType(Type type)
		{
			return type.GetGenericArguments()[0];
		}

		private bool IsNullableType(Type type)
		{
			return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
		}

		public void ChainSelector(ISurrogateSelector selector)
		{
			this._nextSelector = selector;
		}

		public ISurrogateSelector GetNextSelector()
		{
			return this._nextSelector;
		}

		public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
		{
			if (this.IsKnownType(type))
			{
				selector = null;
				return null;
			}
			if (type.IsClass || type.IsValueType)
			{
				selector = this;
				return this;
			}
			selector = null;
			return null;
		}

		private ISurrogateSelector _nextSelector;
	}
}
